Skip to main content

Builder Pattern

  • It is a creational design pattern that lets you construct complex objects step-by-step, separating the construction logic from the final representation.

Useful in situations​

  • An object requires many optional fields, and not all of them are needed every time.
  • You want to avoid telescoping constructors or large constructors with multiple parameters.
  • The object construction process involves multiple steps that need to happen in a particular order.

Initial problem we have​

  • We are building a system to configure the HTTP requests. Each request is mix of some fields where some are required and some are totally optional. like
    • url (required)
    • method (require)
    • headers (optional)
    • body (optiona)
    • ....so on
  • There are two naive approach to build this is to use constructor overloading or telescoping constructor pattern

Constructor overloading​

Each constructor has different sets of parameters, but they don’t chain, leading to repetition.

class HttpRequest {
String url;
String method;
Map<String, String> headers;
Map<String, String> queryParams;
String body;
int timeout;

// Constructor with only URL and method
public HttpRequest(String url, String method) {
this.url = url;
this.method = method;
this.headers = new HashMap<>();
this.queryParams = new HashMap<>();
this.body = "";
this.timeout = 0;
}

// Constructor with URL, method, and headers
public HttpRequest(String url, String method, Map<String, String> headers) {
this(url, method); // This could be chained, but here it's a copy
this.headers = headers;
}

// Constructor with all fields
public HttpRequest(String url, String method, Map<String, String> headers,
Map<String, String> queryParams, String body, int timeout) {
this.url = url;
this.method = method;
this.headers = headers;
this.queryParams = queryParams;
this.body = body;
this.timeout = timeout;
}
}

Telescoping Constructor pattern​

Each constructor calls another, chaining parameters — becomes unreadable with many parameters.

class HttpRequest {
String url;
String method;
Map<String, String> headers;
Map<String, String> queryParams;
String body;
int timeout;

public HttpRequest(String url, String method) {
this(url, method, new HashMap<>());
}

public HttpRequest(String url, String method, Map<String, String> headers) {
this(url, method, headers, new HashMap<>());
}

public HttpRequest(String url, String method, Map<String, String> headers,
Map<String, String> queryParams) {
this(url, method, headers, queryParams, "", 0);
}

public HttpRequest(String url, String method, Map<String, String> headers,
Map<String, String> queryParams, String body, int timeout) {
this.url = url;
this.method = method;
this.headers = headers;
this.queryParams = queryParams;
this.body = body;
this.timeout = timeout;
}
}
  • In above approach we have to remeber full order and pass default values when ever we use it and it has other issue as well like
    • hard to read and write
    • Inflexible as if you want to set parameter 5 but not 3 and 4, you’re forced to pass null for 3 and 4.
    • Adding a new parameter is hard

Builder Pattern​

  • The Builder pattern separates the construction of a complex object from its representation

Class diagram​

  • Builder (e.g., HttpRequestBuilder)

    • Defines methods to configure or set up the product.
    • Typically returns this from each method to support a fluent interface.
    • Often implemented as a static nested class inside the product class.
  • ConcreteBuilder (e.g., StandardHttpRequestBuilder)

    • Implements the Builder interface or defines the fluent methods directly.
    • Maintains state for each part of the product being built.
    • Implements the build() method that returns the final product instance.
  • Product (e.g., HttpRequest)

    • The final object being constructed.
    • May be immutable and built only via the Builder.
    • Has a private constructor that takes in the builder’s internal state.
  • Director (Optional) (e.g., HttpRequestDirector)

    • Orchestrates the building process using the builder.
    • Useful when you want to encapsulate standard configurations or reusable construction sequences.
    • In modern usage (especially in Java with fluent builders), the Director is often omitted, and the client takes on this role by chaining methods.

Implementing Builder​

Create Product Class​

class HttpRequest {
private final String url;
private final String method;
private final Map<String, String> headers;
private final Map<String, String> queryParams;
private final String body;
private final int timeout;

// private called by builder
private HttpRequest(Builder builder) {
this.url = builder.url;
this.method = builder.method;
this.headers = builder.headers;
this.queryParams = builder.queryParams;
this.body = builder.body;
this.timeout = builder.timeout;
}

public static class Builder {
private String url;
private String method;
private Map<String, String> headers = new HashMap<>();
private Map<String, String> queryParams = new HashMap<>();
private String body = "";
private int timeout = 0;

public Builder setUrl(String url) {
this.url = url;
return this;
}

public Builder setMethod(String method) {
this.method = method;
return this;
}

public Builder setHeaders(Map<String, String> headers) {
this.headers = headers;
return this;
}

public Builder setQueryParams(Map<String, String> queryParams) {
this.queryParams = queryParams;
return this;
}

public Builder setBody(String body) {
this.body = body;
return this;
}

public Builder setTimeout(int timeout) {
this.timeout = timeout;
return this;
}

public HttpRequest build() {
return new HttpRequest(this);
}
}
}

Using on client side​

HttpRequest request = new HttpRequest.Builder()
.setUrl("https://api.example.com")
.setMethod("POST")
.setHeaders(Map.of("Authorization", "Bearer token"))
.setBody("{\"key\":\"value\"}")
.setTimeout(5000)
.build();

Benefits​

  • Avoids telescoping constructors and improves readability.
  • Makes object construction flexible and clear.
  • Can enforce immutability and validation before object creation.

When to Use​

  • When an object has many optional parameters.
  • When you want to make the construction process readable and maintainable.
  • When you want to enforce certain construction steps or validation.